﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: XmlMapping.cs
//
//  Description: Holds an XML mapping for one or more persistent objects.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using Chili.Opf3.Relations;
using System.Reflection;
using System.Xml;
using System.IO;

namespace Chili.Opf3.Mapping.Providers
{
	/// <summary>
	/// This class represents an XML mapping for one or more persistent objects.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class holds the mapping for one or more persistent objects. The mapping that's managed by an instance
	/// of this class is based on XML. An <see cref="XmlMapping" /> instance needs to be added to an instance of the
	/// <see cref="XmlTypeMappingProvider" /> class to have the mapping used by the framework.
	/// </para>
	/// <para>
	/// The XML mapping file contains the same data that could also be created by using attributes for the
	/// mapping of persistent objects (the elements of the XML nodes have also the exact same names as the properties
	/// of the attributes). Using the XML mapping means that no attributes, such as
	/// <see cref="FieldAttribute" />, <see cref="PersistentAttribute" /> or <see cref="RelationAttribute" />
	/// need to be specified on/in the persistent objects. The XML mapping can be used as a complete replacement
	/// for the attribute based mapping.
	/// </para>
	/// </remarks>
	/// <example>
	/// The following example shows how such an XML mapping file looks like. The file contains mapping for
	/// one persistent that has a relation (many-to-many) to another persistent.
	/// <code lang="csharp">
	/// &lt;persistents&gt;
	///     &lt;persistent type="ConsoleApplication1.Persistents.Species" entity="SPECIES" poolSize="10" 
	///	        rights="Load, Delete"&gt;
	///	
	///     &lt;fields&gt;
	///         &lt;field member="SpeciesID" identifier="true" allowDBNull="false" /&gt;
	///         &lt;field member="Img" /&gt;
	///         &lt;field member="Family" /&gt;
	///         &lt;field member="Genus" /&gt;
	///         &lt;field member="SpeciesName" fieldName="Species" /&gt;
	///         &lt;field member="Variety" /&gt;
	///         &lt;field member="Symbol" /&gt;
	///     &lt;/fields&gt;
	/// 
	///     &lt;relations&gt;
	///         &lt;relation member="AttributesReference" relations="SpeciesID = SpeciesID"
	///             weakPersistent="ConsoleApplication1.Persistents.SpeciesAttribute"
	///             weakRelations="AttributeID = AttributeID" persistRelationShip="ParentFirst" /&gt;
	///     &lt;/relations&gt;
	///
	///     &lt;/persistent&gt;
	/// &lt;/persistents&gt;
	/// </code>
	/// </example>
	public sealed class XmlMapping
	{
		private XmlDocument _xml;

		/// <summary>
		/// Loads the <see cref="XmlMapping" /> from the given file.
		/// </summary>
		/// <param name="fileName">The file name from where the mapping is to be loaded.</param>
		/// <returns>An instance of the <see cref="XmlMapping" /> that holds the mapping from the given file.</returns>
		public static XmlMapping Load(string fileName)
		{
			if (fileName == null)
				throw new ArgumentNullException("fileName");

			// create the xml document that is used by the mapping.
			XmlDocument xml = new XmlDocument();
			xml.Load(fileName);

			return new XmlMapping(xml);
		}

		/// <summary>
		/// Loads the <see cref="XmlMapping" /> from the given stream.
		/// </summary>
		/// <param name="stream">The stream that contains the XML mapping.</param>
		/// <returns>An instance of the <see cref="XmlMapping" /> that holds the mapping from the given stream.</returns>
		public static XmlMapping Load(Stream stream)
		{
			if (stream == null)
				throw new ArgumentNullException("stream");

			// create the xml document that is used by the mapping.
			XmlDocument xml = new XmlDocument();
			xml.Load(stream);

			return new XmlMapping(xml);
		}

		/// <summary>
		/// Creates an instance of the <see cref="XmlMapping" /> Class.
		/// </summary>
		/// <param name="xml">The XmlDocument holding the mapping from the file.</param>
		private XmlMapping(XmlDocument xml)
		{
			if (xml == null)
				throw new ArgumentNullException("xml");

			_xml = xml;
		}

		/// <summary>
		/// Gets the <see cref="PersistentAttribute" /> for the given type.
		/// </summary>
		/// <param name="type">The type whose <see cref="PersistentAttribute" /> is to be returned.</param>
		internal PersistentAttribute GetPersistentAttribute(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			// get the xml node for the given persistent type.
			XmlNode persistent = GetPersistentNode(type);
			if (persistent == null)
				return null;

			PersistentAttribute attribute = new PersistentAttribute();

			// load the various properties of the attributes.
			XmlAttribute entity = persistent.Attributes["entity"];
			if (entity != null)
				attribute.Entity = entity.Value;
			XmlAttribute poolSize = persistent.Attributes["poolSize"];
			if (poolSize != null)
				attribute.PoolSize = int.Parse(poolSize.Value);
			XmlAttribute rights = persistent.Attributes["rights"];
			if (rights != null)
				attribute.Rights = (PersistentRights)Enum.Parse(typeof(PersistentRights), rights.Value);

			return attribute;
		}

		/// <summary>
		/// Gets the <see cref="FieldAttribute" /> for the given type and member.
		/// </summary>
		/// <param name="type">The type where the <see cref="FieldAttribute" /> should be searched in.</param>
		/// <param name="member">The member for which the <see cref="FieldAttribute" /> is searched.</param>
		internal FieldAttribute GetFieldAttribute(Type type, MemberInfo member)
		{
			if (type == null)
				throw new ArgumentNullException("type");
			if (member == null)
				throw new ArgumentNullException("member");

			// get the xml node for the given persistent type.
			XmlNode persistent = GetPersistentNode(type);
			if (persistent == null)
				return null;

			// get the field node.
			XmlNode field = persistent.SelectSingleNode("./fields/field[@member = '" + member.Name + "']");
			if (field == null)
				return null;

			// create the field attribute.
			FieldAttribute attribute = new FieldAttribute();

			// load the various properties of the attribute.
			XmlAttribute fieldName = field.Attributes["fieldName"];
			if (fieldName != null)
				attribute.FieldName = fieldName.Value;
			XmlAttribute allowDBNull = field.Attributes["allowDBNull"];
			if (allowDBNull != null)
				attribute.AllowDBNull = bool.Parse(allowDBNull.Value);
			XmlAttribute autoNumber = field.Attributes["autoNumber"];
			if (autoNumber != null)
				attribute.AutoNumber = bool.Parse(autoNumber.Value);
			XmlAttribute identifier = field.Attributes["identifier"];
			if (identifier != null)
				attribute.Identifier = bool.Parse(identifier.Value);
			XmlAttribute maxLength = field.Attributes["maxLength"];
			if (maxLength != null)
				attribute.MaxLength = int.Parse(maxLength.Value);
			XmlAttribute persistBehaviours = field.Attributes["persistBehaviours"];
			if (persistBehaviours != null)
				attribute.PersistBehaviours = (PersistBehaviours)Enum.Parse(typeof(PersistBehaviours), persistBehaviours.Value);

			return attribute;
		}

		/// <summary>
		/// Gets the <see cref="RelationAttribute" /> for the given type, member and related type.
		/// </summary>
		/// <param name="type">The type where the <see cref="RelationAttribute" /> should be searched in.</param>
		/// <param name="member">The member for which the <see cref="RelationAttribute" /> is searched.</param>
		/// <param name="relatedType">The related type for which the <see cref="RelationAttribute" /> is searched for.</param>
		internal RelationAttribute GetRelationAttribute(Type type, MemberInfo member, Type relatedType)
		{
			if (type == null)
				throw new ArgumentNullException("type");
			if (member == null)
				throw new ArgumentNullException("member");
			if (relatedType == null)
				throw new ArgumentNullException("relatedType");

			// get the xml node for the given persistent type.
			XmlNode persistent = GetPersistentNode(type);
			if (persistent == null)
				return null;

			// get the relation node.
			XmlNode relation = persistent.SelectSingleNode("./relations/relation[@member = '" + member.Name + "']");
			if (relation == null)
				return null;

			// load the relations attribute from the node.
			XmlAttribute relations = relation.Attributes["relations"];
			if (relations == null)
				throw new MappingException("The specified relation has no foreign key relation specified.");

			// create the field attribute.
			RelationAttribute attribute = new RelationAttribute(relations.Value);

			// load the various properties of the attribute.
			XmlAttribute weakPersistent = relation.Attributes["weakPersistent"];
			if (weakPersistent != null)
			{
				// try to get the type from the name provided.
				Type weakPersistentType = Type.GetType(weakPersistent.Value);
				// if that didn't work out try to get the type from the entry assembly.
				if (weakPersistentType == null)
					weakPersistentType = Assembly.GetEntryAssembly().GetType(weakPersistent.Value, true);
				attribute.WeakPersistent = weakPersistentType;
			}

			XmlAttribute weakRelations = relation.Attributes["weakRelations"];
			if (weakRelations != null)
				attribute.WeakRelations = weakRelations.Value;
			XmlAttribute name = relation.Attributes["name"];
			if (name != null)
				attribute.Name = name.Value;
			XmlAttribute persistBehaviours = relation.Attributes["persistBehaviours"];
			if (persistBehaviours != null)
				attribute.PersistBehaviours = (PersistBehaviours)Enum.Parse(typeof(PersistBehaviours), persistBehaviours.Value);
			XmlAttribute persistRelationShip = relation.Attributes["persistRelationShip"];
			if (persistRelationShip != null)
				attribute.PersistRelationship = (PersistRelationships)Enum.Parse(typeof(PersistRelationships), 
					persistRelationShip.Value);

			return attribute;
		}

		/// <summary>
		/// Gets the node for the given persistent type.
		/// </summary>
		/// <param name="type">The persistent type for which the node is returned.</param>
		private XmlNode GetPersistentNode(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			// try to get the node that contains the persistent mapping.
			return _xml.SelectSingleNode("./persistents/persistent[@type = '" + type.FullName + "']");
		}
	}
}
